package com.younio.local.service;

import com.alibaba.fastjson.JSON;
import com.younio.local.compnent.WebSocketServer;
import com.younio.local.entity.Jb;
import com.younio.local.entity.Line2.Line;
import com.younio.local.entity.TI;
import com.younio.local.entity.TbNo;
import com.younio.local.entity.TidInfo;
import com.younio.local.repository.JbMapper;
import com.younio.local.repository.JbMapper2;
import com.younio.local.util.Objects;
import com.younio.local.util.QueryParam;
import com.younio.local.util.Result;
import com.younio.local.util.Sequence;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.younio.local.util.QueryParam.builder;

@Slf4j
@Service
@EnableScheduling
public class JbService2 {

    @Autowired
    private JbMapper jbMapper;

    @Autowired
    private JbMapper2 jbMapper2;

    @Autowired
    private WebSocketServer wsServer;

    @Autowired
    private Executor executor;

    @Autowired
    private RedisTemplate<String, Boolean> redisTemplate;

    private Symbol symbol;

    public JbService2(Symbol symbol) {
        this.symbol = symbol;
        this.redisPrefix = symbol.getPcSign() + "_line:";
    }

    private String redisPrefix;

    private final ConcurrentMap<Integer, Long> indexMap = new ConcurrentHashMap<>();

    /**
     * @desc 待处理的line暂存在这里
     * @author lan [1728209643@qq.com]
     * @create 2019/1/2 15:35
     */
    private final BlockingQueue<Line> lineQueue = new ArrayBlockingQueue<>(4);

    private Integer[] lines = {1, 2, 3, 4};

    private boolean cancel;

    private final static AtomicBoolean initedIndexMap = new AtomicBoolean(Boolean.FALSE);

    public void remove(String uuid) {
        try {
            Boolean res = redisTemplate.opsForValue().get(redisPrefix + uuid);
            if (res != null && res) {
                throw new RuntimeException("已完成绑定");
            }
            cancel = true;
            synchronized (lineQueue) {
                Optional<Line> any = lineQueue.stream().filter(line -> uuid.equals(line.getUuid())).findAny();
                if (any.isPresent()) {
                    lineQueue.remove(any.get());
                    return;
                }
            }
            res = redisTemplate.opsForValue().get(redisPrefix + uuid);
            if (res != null && res) {
                throw new RuntimeException("已完成绑定");
            } else {
                throw new RuntimeException("不存在该记录");
            }
        } finally {
            cancel = false;
        }
    }

    /**
     * @desc 把请求都放到lineQueue中, 校验(合法性校验 / 芯片是否初始化 / 所有芯片是否属于同一订单 / 该次检表记录是否已经保存) + 初始化
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:03
     */
    public Result addTask(Line line) {
        Objects.requireNonNull(line, "参数错误");
        Objects.checkArguments(line.getNum() > 0, "参数错误");
        line.isInited();
        QueryParam param = QueryParam.builder().add("uuid", line.getUuid());
        Result result = Result.builder().build();
        Integer line_ = line.getLine();
        while (!initedIndexMap.get()) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (indexMap) {
            Objects.checkArguments(indexMap.containsKey(line_), "参数错误");
        }


        Boolean res = redisTemplate.opsForValue().get(redisPrefix + line.getUuid());
        if (res != null && res) {
            throw new RuntimeException("已完成绑定");
        }
        redisTemplate.opsForValue().set(redisPrefix + line.getUuid(), Boolean.FALSE);

        if (line.hasTid() && !line.isIgnoreErr()) {
            Object o = checkTidList(line.getTiList());
            if (o != null) {
                param.add("errors", o);
                result.setCode(400);
                param.add("line", line);
                result.setData(param);
                return result;
            }
        }
        synchronized (lineQueue) {
            Optional<Line> optional = lineQueue.stream()
                    .filter(l -> l.getLine() == line.getLine())
                    .findAny();
            optional.ifPresent(lineQueue::remove);
            resetIndex(line.getLine() - 1);

            lineQueue.add(line);
        }
        return Result.OK(param);
    }

    /**
     * @desc 重置indexMap中的记录
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:06
     */
    public synchronized void resetIndex() {
        synchronized (indexMap) {
            for (int i = 0; i < lines.length; i++) {
                indexMap.put(lines[i], Objects.defaultValue(jbMapper2.maxId(lines[i] - 1)));
                log.info((i + 1) + "线最大索引为: " + indexMap.get(lines[i]));
            }
            initedIndexMap.set(Boolean.TRUE);
        }
    }

    /**
     * @desc 重置indexMap中的记录
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:06
     */
    private void resetIndex(int line) {
        synchronized (indexMap) {
            indexMap.put(line + 1, Objects.defaultValue(jbMapper2.maxId(line)));
            log.info((line + 1) + "线最大索引为: " + indexMap.get(line + 1));
        }
    }

    /**
     * @desc 定时任务保存数据, 保存成功则广播通知
     * @author lan [1728209643@qq.com]
     * @create 2018/12/20 9:09
     */
    @Scheduled(cron = "* * * * * ?")
//    @Scheduled(cron = "0/10 * * * * ?")
    public void tnmaSavedData() {
        //取消状态下暂时不执行，让出锁
        if (!cancel) {
            synchronized (lineQueue) {
                List<Line> reslovedList = new LinkedList<>();
                lineQueue.forEach(line -> {
                    try {
                        if (handler(line)) {
                            lineQueue.remove(line);
                            redisTemplate.opsForValue().set(redisPrefix + line.getUuid(), Boolean.TRUE);
                            wsServer.broadcast(JSON.toJSONString(Result.OK(line.getUuid())));
                            reslovedList.add(line);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        wsServer.broadcast(
                                JSON.toJSONString(
                                        Result.FAILED(
                                                builder()
                                                        .add("uuid", line.getUuid())
                                                        .add("errMsg", e.getLocalizedMessage()))));
                    }
                });
                reslovedList.forEach(lineQueue::remove);
            }
        }
    }

    /**
     * @desc 先从天马查询到新增加的检表记录    ->  初始化(计算Q值)   ->  设置tid   ->  resetIndex()
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:00
     */
    private boolean handler(Line line) {
        List<Jb> jbList;
        synchronized (indexMap) {
            jbList = getNew(line, indexMap.get(line.getLine()), line.getNum());
        }
        if (jbList == null) {
            return false;
        }
        log.info(JSON.toJSONString(jbList));

        line.isInited();
        jbList.forEach(Jb::init);
        //无芯片的设置一个虚拟tid seq+"#"
        jbList.forEach(jb -> jb.setTid(line.getTid(jb.getIndex())));

        if (save(jbList, line)) {
            resetIndex(line.getLine() - 1);
            executor.execute(new TbRunner(jbList));

        }
        return true;
    }


    /**
     * 查询数据库，绑定表号和matUuid
     */
    class TbRunner implements Runnable {

        private List<Jb> jbList;

        TbRunner(List<Jb> jbList) {
            this.jbList = jbList;
        }

        @Override
        public void run() {
            try {
                List<TbNo> tbNoList = new LinkedList<>();
                jbList.forEach(jb -> {
                    if (Objects.strIsNotEmpty(jb.getTb())) {
                        String prefix = TbNo.getPrefix(jb.getTb());
                        List<TbNo> tbNoListByPrefix = getTbNoListByPrefix(prefix);
                        if (tbNoListByPrefix != null) {
                            BigInteger val = new BigInteger(jb.getTb().replace(prefix, ""));
                            Optional<TbNo> optionalTbNo = tbNoListByPrefix.stream().filter(tbNo -> TbNo.match(tbNo, val)).findAny();
                            if (optionalTbNo.isPresent()) {
                                TbNo tbNo = optionalTbNo.get();
                                tbNo.setVal(val);
                                tbNo.setTbValue(jb.getTb());
                                tbNoList.add(tbNo);
                            }
                        }

                    }
                });
                save(tbNoList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean save(List<Jb> jbList, Line line) {
        //为了避免有表号的也使用了条码
        boolean hasTb = false;
        if (jbList != null && jbList.size() > 0) {
            hasTb = Objects.strIsNotEmpty(jbList.get(0).getTb());
        }
        jbMapper.saveResult(
                builder()
                        .add("jbList", jbList)
                        .add("line", line)
                        .add("hasTid", hasTb ? 0 : line.hasTid())
                        .add("sequence", Sequence.get())
                        .add("tnmaMac", symbol.getPcSign())
                        .add("operateTime", new Date())
        );
        log.info("---------------------------------------------------------------------------------------------");
        log.info("jbList: " + jbList);
        log.info("line：" + line);
        log.info("---------------------------------------------------------------------------------------------");
        return true;
    }


    /**
     * @param line 线
     * @param from 起始id
     * @param num  数量
     * @return java.util.List<com.younio.local.entity.Jb>   有正确数据时返回正确结果,无数据是返回null
     * @desc getNew 获取tnma保存的新数据
     * 先查询meter_item中是否有数据增加,得到meterIdList
     * 根据meterIdList查询完整的校表数据
     * 校验每一项的Q项是否相同,不相同即未保存完毕返回null
     * @author lan [1728209643@qq.com]
     * @create 2019/1/2 16:44
     */
    private List<Jb> getNew(Line line, long from, int num) {
        List<Long> meterIdList = jbMapper2.getMeterItemNewIDList(from, line.getLine() - 1);
        if (meterIdList != null && !meterIdList.isEmpty() && meterIdList.size() >= num) {
//            meterIdList = meterIdList.size() > num ? meterIdList.subList(0, num) : meterIdList;
            List<Jb> jbList = jbMapper.findMore(meterIdList);
            if (jbList != null) {
                Map<Integer, Jb> jbMap = jbList.stream().distinct().collect(Collectors.toMap(Jb::getIndex, jb -> jb));
                Set<Integer> set = line.getIndexSet();
                jbList = new LinkedList<>();
                List<Jb> finalJbList = jbList;
                set.forEach(index -> {
                    Jb jb = jbMap.get(index);
                    if (jb != null) {
                        finalJbList.add(jb);
                    }
                });
                return finalJbList;
            }
//            if (jbList.size() == num) {
//                Map<Integer, List<Jb>> map = jbList.stream().collect(
//                        Collectors.groupingBy(jb -> jb.getQnList() == null ? -1 : jb.getQnList().size()));
//                if (map.keySet().stream().distinct().count() == 1
//                        && map.keySet().stream().findAny().get() != -1) {
//                    return jbList;
//                }
//            }
        }
        return null;
    }

    /**
     * @desc 校验芯片是否校验/初始化, 是否属于同一订单
     * @author lan [1728209643@qq.com]
     * @create 2019/1/2 10:02
     */
    private Object checkTidList(List<TI> tiList) {
        List<String> tidList = tiList.stream().map(TI::getTid).collect(Collectors.toList());
        Map<Integer, List<String>> errMap = new HashMap<>();
        Set<String> matUuidSet = new HashSet<>();
        AtomicInteger index = new AtomicInteger(1);
        Map<Integer, TidInfo> tidInfoMap = new HashMap<>();
        tidList.forEach(tid -> {
            TidInfo tidInfo = jbMapper.getTidInfo(tid);
            if (tidInfo != null) {
                tidInfoMap.put(index.get(), tidInfo);
            }

            //是否初始化
            Objects.test(tidInfo == null
                            || tidInfo.isInit()
                            || Objects.strIsNotEmpty(tidInfo.getMatUuid()),
                    tidInfo,
                    tidInfo1 -> {
                        List<String> errList = errMap.getOrDefault(index.get(), new LinkedList<>());
                        errList.add("没有初始化");
                        errMap.put(index.get(), errList);
                    });
            if (tidInfo != null) {
                matUuidSet.add(tidInfo.getIndentId() + tidInfo.getMatId());
            }

            index.getAndIncrement();
        });
        //是否属于同一订单
        if (matUuidSet.size() > 1) {
            int size = tidList.size();
            for (int i = 1; i < size + 1; i++) {
                List<String> errList = errMap.computeIfAbsent(i, k -> new LinkedList<>());
                TidInfo tidInfo = tidInfoMap.get(i);
                if (tidInfo != null
                        && Objects.strIsNotEmpty(tidInfo.getMatUuid())
                        && Objects.strIsNotEmpty(tidInfo.getMatId())
                        && Objects.strIsNotEmpty(tidInfo.getIndentId())) {
                    errList.add("属于订单[" + tidInfo.getIndentId() + "]，物料编码为" + tidInfo.getMatId());
                }
            }
        }
        return Objects.emptyToNull(errMap);
    }

    private void save(List<TbNo> tbNoList) {
        tbNoList = Objects.emptyToNull(tbNoList);
        if (tbNoList != null) {
            jbMapper.saveTbNoList(tbNoList);
        }
    }

    private List<TbNo> getTbNoListByPrefix(String prefix) {
        if (Objects.strIsNotEmpty(prefix)) {
            return Objects.emptyToNull(jbMapper.getTbNoListByPrefix(prefix));
        }

        return null;
    }


}
